package com.alks.common.aspect;


import cn.hutool.core.convert.Convert;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alks.common.data.dto.SysUserTokenDTO;
import com.alks.common.data.thread.UserIdThread;
import com.alks.common.service.ILogService;
import com.alks.common.utils.ByteUtils;
import com.alks.entity.data.entity.Global;
import com.alks.entity.data.entity.sys.SysLog;
import com.alks.entity.exception.ServiceErrorException;
import com.baomidou.mybatisplus.extension.api.R;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.concurrent.Executor;


@Aspect
@Component
@Slf4j
public class SysLogAspect {

    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    private final Executor customThreadPoolTaskExecutor;

    private final ILogService sysLogService;


	public SysLogAspect(Executor customThreadPoolTaskExecutor,  ILogService sysLogService) {
		this.customThreadPoolTaskExecutor = customThreadPoolTaskExecutor;
		this.sysLogService = sysLogService;
	}

	/**
	 * 日志切点
	 */
	@Pointcut("@annotation(com.alks.common.aop.SysLogAop)")
	public void sysLog() {}

	/**
	 * 前置通知
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Before(value = "sysLog()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		HttpServletRequest request = ((ServletRequestAttributes) Objects
				.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

		SysLog sysLog = new SysLog();
		// 创建人信息请根据实际项目获取方式获取
		SysUserTokenDTO sysUserTokenDTO = UserIdThread.get();
		sysLog.setCreateUserCode(sysUserTokenDTO.getUserId());
		sysLog.setCreateUserName(sysUserTokenDTO.getUserName());
		sysLog.setCompanyId(sysUserTokenDTO.getCompanyId());
		sysLog.setStartTime(LocalDateTime.now());
		//sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest uri = attributes.getRequest();
		sysLog.setRequestUri(uri.getRequestURI().toString());

		//获取请求参数
		//sysLog.setRequestParams(formatParams(request.getParameterMap()));
		// 获取请求参数
    	Object[] args = joinPoint.getArgs();
    	ObjectMapper objectMapper = new ObjectMapper();

    	try {
    	    if (args.length == 1) {
    	        // 如果只有一个参数，直接转换该参数
    	        String operationObjectJson = objectMapper.writeValueAsString(args[0]);
    	        log.info("请求参数：" + operationObjectJson);
    	        sysLog.setRequestParams(operationObjectJson);
    	    } else {
    	        // 如果有多个参数，转换整个数组
    	        String operationObjectJson = objectMapper.writeValueAsString(args);
    	        log.info("请求参数：" + operationObjectJson);
    	        sysLog.setRequestParams(operationObjectJson);
    	    }
    	} catch (Exception e) {
    	    throw new ServiceErrorException("JSON转换失败: " + e.getMessage());
    	}

		sysLog.setRequestMethod(request.getMethod());
		sysLog.setRequestIp(ServletUtil.getClientIP(request));
		sysLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
		String userAgentStr = request.getHeader("User-Agent");
		sysLog.setUserAgent(userAgentStr);
		UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
		sysLog.setDeviceName(userAgent.getOs().getName());
		sysLog.setBrowserName(userAgent.getBrowser().getName());

		sysLogThreadLocal.set(sysLog);

		log.info("开始计时: {}  URI: {}  IP: {}", sysLog.getStartTime(), sysLog.getRequestUri(), sysLog.getRequestIp());
	}

	/**
	 * 返回通知
	 * @param ret
	 */
	@AfterReturning(pointcut = "sysLog()", returning = "ret")
	public void doAfterReturning(Object ret) {
		SysLog sysLog = sysLogThreadLocal.get();
		sysLog.setLogType(Global.LOG_INGO);
		sysLog.setEndTime(LocalDateTime.now());
		sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MILLIS.between(sysLog.getStartTime(), sysLog.getEndTime())));
		R<?> r = Convert.convert(R.class, ret);
		//if (r.getCode() == Global.TRUE) {
		if (r.getCode() == 200) {
			sysLog.setIsException(Global.NO);
		} else {
			sysLog.setIsException(Global.YES);
			sysLog.setExceptionInfo(r.getMsg());
		}
		customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
		sysLogThreadLocal.remove();

		Runtime runtime = Runtime.getRuntime();
		log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
				sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
				ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
	}

	/**
	 * 异常通知
	 * @param e
	 */
	@AfterThrowing(pointcut = "sysLog()", throwing = "e")
	public void doAfterThrowable(Throwable e) {
		SysLog sysLog = sysLogThreadLocal.get();
		sysLog.setLogType(Global.LOG_ERROR);
		sysLog.setEndTime(LocalDateTime.now());
		sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(sysLog.getStartTime(), sysLog.getEndTime())));
		sysLog.setIsException(Global.YES);
		sysLog.setExceptionInfo(e.getMessage());
		customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
		sysLogThreadLocal.remove();

		Runtime runtime = Runtime.getRuntime();
		log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
				sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
				ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
	}



	/**
	 * 保存日志线程
	 */
	private static class SaveLogThread extends Thread {
		private SysLog sysLog;
		private ILogService sysLogService;

		public SaveLogThread(SysLog sysLog, ILogService sysLogService) {
			this.sysLog = sysLog;
			this.sysLogService = sysLogService;
		}

		@Override
		public void run() {
			sysLog.setCreateDate(LocalDateTime.now());
			sysLogService.save(sysLog);
		}
	}


}